package com.yunda.AJDeviceService.common.service;

import com.alibaba.fastjson.JSON;
import com.yunda.AJDeviceService.common.constant.Constants;
import com.yunda.AJDeviceService.utils.DataUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author qianhj
 * @version 1.0.0
 * @ClassName RedisService.java
 * @Description spring redis
 * @createTime 2021年08月03日 10:03:00
 */
@Slf4j
@Component
public class RedisService {

    @Autowired
    public RedisTemplate redisTemplate;

    @Resource(name="redisTemplate")
    private ValueOperations<String, String> valueOperations;

    @Resource(name="redisTemplate")
    private HashOperations<String, String, Object> hashOperations;

    @Resource(name="redisTemplate")
    private ListOperations<String, Object> listOperations;

    @Resource(name="redisTemplate")
    private SetOperations<String, Object> setOperations;

    @Resource(name="redisTemplate")
    private ZSetOperations<String, Object> zSetOperations;

    /**  默认过期时长，单位：秒 */
    public final long DEFAULT_EXPIRE = 60 * 60 * 24;
    /**  不设置过期时长 */
    public final long NOT_EXPIRE = -1;
    /**  失败数据下载redis 过期时长，单位：秒 */
    public final long FAILED_DATA_EXPIRE =  60 * 10;

    /**   分布式锁执行结果  */
    public final String DISTRIBUTED_LOCK_RESULT = "1";

    public RedisService (){
        log.debug("------------->  RedisUtils 初始化---------------------------");
    }

    public void setBit(String k,long l,boolean b){
        setBit(k,l,b,NOT_EXPIRE);
    }

    public void setBit(String k,long l,boolean b, long expire){
        valueOperations.setBit(k,l,b);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(k, expire, TimeUnit.SECONDS);
        }
    }

    public Long bitCount(final String key) {
        return (Long) redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
    }

    public Long bitCount(String key, int start, int end) {
        return (Long) redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes(), start, end));
    }

    public Long bitOp(RedisStringCommands.BitOperation op, String saveKey, String... desKey) {
        byte[][] bytes = new byte[desKey.length][];
        for (int i = 0; i < desKey.length; i++) {
            bytes[i] = desKey[i].getBytes();
        }
        return (Long) redisTemplate.execute((RedisCallback<Long>) con -> con.bitOp(op, saveKey.getBytes(), bytes));
    }

    public void set(String key, Object value, long expire){
        valueOperations.set(key, toJson(value));
//    	redisTemplate.opsForValue().set(key, toJson(value));

        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public void set(String key, Object value){

        log.debug(Constants.LOG_PRE_FIX + "set-------key:{},value:{}",key,toJson(value));

        set(key, value, DEFAULT_EXPIRE);
    }

    public <T> T get(String key, Class<T> clazz, long expire) {
        String value = valueOperations.get(key);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value == null ? null : fromJson(value, clazz);
    }

    public <T> T get(String key, Class<T> clazz) {
        return get(key, clazz, NOT_EXPIRE);
    }

    public String get(String key, long expire) {
        String value = valueOperations.get(key);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value;
    }

    public String get(String key) {
        return get(key, NOT_EXPIRE);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /** 添加对象到redis 里面的list中
     *  redis中的 list 是双向的 所以添加的时候需要注意
     *  rightPush 先进先出 leftPush 先进后出 这里 需要注意
     * @param key list 对应的key
     * @param obj 需要存的对象
     */
    public void addList(String key,Object obj, long expire){
        listOperations.rightPush(key, toJson(obj));
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public void addList(String key,Object obj){
        addList(key, obj, NOT_EXPIRE);
    }

    /**
     * opsForList().range(key, start, end);  取范围值  redis里面的list下标从0开始
     *  流程 拿到key 对应的list 取 0 到 5  和 mysql的limt  类似 注意下标即可
     * 从redis list 里面的获取数据分页
     * @param key redis list 对应的key
     * @param start  开始下标
     * @param end 介绍下标
     * @return 返回list给前端
     */
    public <T> List<T> getList(String key, int start, int end,Class<T> clazz, long expire){
        List<Object> list = listOperations.range(key, start, end);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        List<T> valueList = new ArrayList<T>();
        for(Object value : list){
            valueList.add(fromJson(DataUtils.toString(value), clazz));
        }

        return valueList;
    }

    public List<String> getList(String key, int start, int end, long expire){
        List<Object> list = listOperations.range(key, start, end);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        List<String> valueList = new ArrayList<String>();
        for(Object value : list){
            valueList.add(DataUtils.toString(value));
        }

        return valueList;
    }

    public <T> List<T> getList(String key, int start, int end,Class<T> clazz){
        return getList(key, start, end, clazz, NOT_EXPIRE);
    }

    public List<String> getList(String key, int start, int end){
        return getList(key, start, end, NOT_EXPIRE);
    }

    /**
     *
     * redis  hash操作
     *
     *
     *
     */
    /**
     * 存放一组属性值（或者说是对象的多个属性）
     * @param key
     * @param map
     * @param expire
     */
    public void hsetAllFields(String key,Map<String,String> map, long expire){

        log.debug(Constants.LOG_PRE_FIX + "hsetAll-------key:{},map:{}",key,map);

        Set<String> set = map.keySet();
        Iterator<String> i = set.iterator();
        while(i.hasNext()){
            String field = i.next();
            String value = toJson(map.get(field));

            map.put(field, value);
        }


        hashOperations.putAll(key, map);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }
    /**
     * 存放单个属性值
     * @param key
     * @param field
     * @param value
     * @param expire
     */
    public void hset(String key, String field,Object value, long expire){

        log.debug(Constants.LOG_PRE_FIX + "hset-------key:{},field:{},value:{}",key,field,toJson(value));

        hashOperations.put(key,field,toJson(value));
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    /**
     * 获取对象所有属性
     * @param key
     * @param expire
     * @return
     */
    public Map hgetAll(String key, long expire) {
        Map map = hashOperations.entries(key);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return map;
    }

    /**
     * 获取对象所有属性
     * @param key
     * @return
     */
    public Map hgetAll(String key) {
        return hgetAll(key,NOT_EXPIRE);
    }

    /**
     * 获取对象单个属性
     * @param key
     * @param field
     * @param expire
     * @return
     */
    public String hget(String key, String field,long expire) {
        Object obj = hashOperations.get(key, field);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return DataUtils.toString(obj);
    }

    /**
     * 删除属性
     * @param key
     * @param fields
     */
    public void hdel(String key,String... fields){
        hashOperations.delete(key, fields);
    }

    /**
     * 获取对象单个属性
     * @param key
     * @param field
     * @return
     */
    public String hget(String key, String field) {
        return hget(key,field,NOT_EXPIRE);
    }

    /**
     * 计数
     * @param key
     * @param field
     * @param expire
     * @return
     */
    public long hCountTimes(String key, String field,long expire) {
        log.debug(Constants.LOG_PRE_FIX + "hCountTimes-------key:{},field:{}",key,field);

        long times = hashOperations.increment(key,field,1);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }

        return times;
    }

    /**
     * 向set添加单个值
     * @param key
     * @param value
     * @param expire
     */
    public void sset(String key, Object value, long expire){
        String val = toJson(value);
        log.debug(Constants.LOG_PRE_FIX + "sset-------key:{},value:{}",key,val);

        setOperations.add(key, val);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public void ssetAll(String key, Set<Object> vals){
        for (Object val : vals) {
            sset(key, val, NOT_EXPIRE);
        }
    }

    /**
     * 获取set中的值
     * @param key
     * @return
     */
    public Set<Object> sget(String key){
        return setOperations.members(key);
    }

    /**
     * 移除set中的值
     * @param key
     * @param val
     */
    public void sremove(String key,Object val){
        setOperations.remove(key, toJson(val));
    }


    /**
     * 向zset添加单个值
     * @param key
     * @param value
     * @param score
     * @param expire
     */
    public void zset(String key,Object value,double score,long expire){
        String val = toJson(value);
        log.debug(Constants.LOG_PRE_FIX + "zset-------key:{},value:{}",key,val);
        zSetOperations.add(key,val,score);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    /**
     * 向zset添加单个值
     * @param key
     * @param value
     * @param score
     */
    public void zset(String key,Object value,double score){
        zset(key,value,score,NOT_EXPIRE);
    }

    /**
     * 根据分数获取zset数据分页
     * @param key
     * @param min
     * @param max
     * @param offset
     * @param pageSize
     * @return
     */
    public Set<ZSetOperations.TypedTuple<Object>> rangeByScoreWithScores(String key, long min, long max, long offset, long pageSize){
        log.debug(Constants.LOG_PRE_FIX + "rangeByScoreWithScores--> key:{},min:{},max:{},offset:{},pageSize:{}",key,min,max,offset,pageSize);
        return zSetOperations.rangeByScoreWithScores(key,min,max,offset,pageSize);
    }

    /**
     * 根据分数获取zset总数
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Long getZsetCount(String key,double min,double max){
        return zSetOperations.count(key,min,max);
    }


    /**
     *
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expire 过期时间(秒)
     * @return 是否获的锁
     */
    public boolean getDistributedLock(String lockKey,String requestId, long expire){
        boolean ret = false;
        try{
            String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";

            RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);

            Object result = redisTemplate.execute(redisScript,new StringRedisSerializer(),new StringRedisSerializer(), Collections.singletonList(lockKey),requestId,expire + "");
            log.debug(Constants.LOG_PRE_FIX + "获取分布式锁，锁：{}，结果：{}",lockKey,DataUtils.toString(result));

            if(DISTRIBUTED_LOCK_RESULT.equals(DataUtils.toString(result))){
                return true;
            }

        }catch(Exception e){
            log.error(Constants.LOG_PRE_FIX + "获取分布式锁出现错误{}",e);
        }
        return ret;
    }

    /**
     * 释放锁：释放锁时判断是不是该锁，防止超时锁自动释放后，将其他节点的锁给释放了
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return
     */
    public boolean releaseDistributedLock(String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);

        Object result = redisTemplate.execute(redisScript, new StringRedisSerializer(), new StringRedisSerializer(), Collections.singletonList(lockKey), requestId);
        if(DISTRIBUTED_LOCK_RESULT.equals(DataUtils.toString(result))){
            return true;
        }

        return false;
    }


    /**
     * Object转成JSON数据
     */
    private String toJson(Object object){

        if(object == null){
            return "";
        }

        if(object instanceof Integer || object instanceof Long || object instanceof Float ||
                object instanceof Double || object instanceof Boolean || object instanceof String){
            return String.valueOf(object);
        }
        return JSON.toJSONString(object);
    }


    /**
     * JSON数据，转成Object
     */
    private <T> T fromJson(String json, Class<T> clazz){
        return JSON.parseObject(json, clazz);
    }
}
